package jodd.mail;

import cn.hutool.core.date.DateUtil;
import jakarta.mail.FetchProfile;
import jakarta.mail.Flags;
import jakarta.mail.Message;
import jakarta.mail.MessagingException;

import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;

public class ReceivedEmails {
    private final Message[] messages;
    private final boolean envelope;
    private final ReceiveMailSession session;
    private final Flags flagsToSet;
    private final Flags flagsToUnset;
    private final Consumer<Message[]> processedMessageConsumer;

    public ReceivedEmails(
            final ReceiveMailSession session,
            final Message[] messages,
            final Flags flagsToSet,
            final Flags flagsToUnset,
            final boolean envelope,
            final Consumer<Message[]> processedMessageConsumer
    ) {
        this.session = session;
        this.messages = messages;
        this.flagsToSet = flagsToSet;
        this.flagsToUnset = flagsToUnset;
        this.envelope = envelope;
        this.processedMessageConsumer = processedMessageConsumer;
    }

    public ReceivedEmail[] fetch() {
        try {
            return _fetch2();
        } catch (final MessagingException ex) {
            throw new MailException("Failed to process fetched messages", ex);
        }
    }

    public void fetchCallBack(ReceivedCallback callback) {
        try {
            _fetchCallback(callback);
        } catch (final MessagingException ex) {
            throw new MailException("Failed to process fetched messages", ex);
        }
    }

    private void _fetchCallback(ReceivedCallback callback) throws MessagingException {
        if (messages.length == 0) {
            return;
        }

        if (envelope) {
            final FetchProfile fetchProfile = new FetchProfile();

            fetchProfile.add(FetchProfile.Item.ENVELOPE);
            fetchProfile.add(FetchProfile.Item.FLAGS);

            session.folder.fetch(messages, fetchProfile);
        }

        ReceivedEmail email;

        Date minDate = DateUtil.parseDateTime("2024-06-04 00:00:00");

        for (int i = 0; i < messages.length; i++) {

            final Message msg = messages[i];
            System.out.println(" start fetch email: " + i + "/" + messages.length);

            //如果发送时间小于minDate，则跳过
            if (msg.getSentDate().before(minDate)) {
                System.out.println(DateUtil.formatDateTime(msg.getSentDate()) + "小于限制的最小时间，跳过不处理");
                continue;
            }

            /*===[4089]===
FROM:cgbj@bayblue.hk(cgbj@bayblue.hk), SUBJECT:BayBlue     【pre-order  预订】     RevitaLash/瑞瓦德      多品类      报价 2024.06.04, SENT DATE:2024-06-04 12:06:28
附件列表...
name: CatchBBE2(06-04-(06-04-12-05-23).jpgname: InsertPic_7FC4(0(06-04-12-05-23).jpgname: 2024.06.04-RevitaLash瑞瓦德 多品类.xlsx start fetch email: 4089/4730
download email:633/4730*/

            // we need to parse message BEFORE flags are set!
            email = new ReceivedEmail(msg, envelope, session.attachmentStorage);

            if (!EmailUtil.isEmptyFlags(flagsToSet)) {
                email.flags(flagsToSet);
                msg.setFlags(flagsToSet, true);
            }

            if (!EmailUtil.isEmptyFlags(flagsToUnset)) {
                email.flags().remove(flagsToUnset);
                msg.setFlags(flagsToUnset, false);
            }

            if (EmailUtil.isEmptyFlags(flagsToSet) && !email.isSeen()) {
                msg.setFlag(Flags.Flag.SEEN, false);
            }

            callback.dealEmail(email);
        }
    }

    /**
     * 优化后的_fetch方法，采用多线程处理每条消息。
     */
    private ReceivedEmail[] _fetch() throws MessagingException {
        if (messages.length == 0) {
            return ReceivedEmail.EMPTY_ARRAY;
        }

        if (envelope) {
            final FetchProfile fetchProfile = new FetchProfile();

            fetchProfile.add(FetchProfile.Item.ENVELOPE);
            fetchProfile.add(FetchProfile.Item.FLAGS);

            session.folder.fetch(messages, fetchProfile);
        }

        final ReceivedEmail[] emails = new ReceivedEmail[messages.length];
        int nThreads = Runtime.getRuntime().availableProcessors();
        ExecutorService executor = Executors.newFixedThreadPool(nThreads);

        for (int i = 0; i < messages.length; i++) {
            final Message msg = messages[i];
            final int index = i;

            // 提交任务到线程池
            executor.submit(() -> {
                try {
                    // we need to parse message BEFORE flags are set!
                    long tid = Thread.currentThread().getId();
                    System.out.println(tid + " start fetch email: " + index + "/" + messages.length);
                    emails[index] = new ReceivedEmail(msg, envelope, session.attachmentStorage);

                    if (!EmailUtil.isEmptyFlags(flagsToSet)) {
                        emails[index].flags(flagsToSet);
                        msg.setFlags(flagsToSet, true);
                    }

                    if (!EmailUtil.isEmptyFlags(flagsToUnset)) {
                        emails[index].flags().remove(flagsToUnset);
                        msg.setFlags(flagsToUnset, false);
                    }

                    if (EmailUtil.isEmptyFlags(flagsToSet) && !emails[index].isSeen()) {
                        msg.setFlag(Flags.Flag.SEEN, false);
                    }
                } catch (MessagingException e) {
                    // 处理异常
                    e.printStackTrace();
                }
            });
        }

        // 等待所有任务完成
        executor.shutdown();
        try {
            executor.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            throw new RuntimeException(e);
        }

        if (processedMessageConsumer != null) {
            processedMessageConsumer.accept(messages);
        }

        // if messages were marked to be deleted, we need to expunge the folder
        if (!EmailUtil.isEmptyFlags(flagsToSet)) {
            if (flagsToSet.contains(Flags.Flag.DELETED)) {
                session.folder.expunge();
            }
        }

        return emails;
    }

    private ReceivedEmail[] _fetch2() throws MessagingException {
        if (messages.length == 0) {
            return ReceivedEmail.EMPTY_ARRAY;
        }

        if (envelope) {
            final FetchProfile fetchProfile = new FetchProfile();

            fetchProfile.add(FetchProfile.Item.ENVELOPE);
            fetchProfile.add(FetchProfile.Item.FLAGS);

            session.folder.fetch(messages, fetchProfile);
        }

        final ReceivedEmail[] emails = new ReceivedEmail[messages.length];

        for (int i = 0; i < messages.length; i++) {

            final Message msg = messages[i];
            System.out.println(" start fetch email: " + i + "/" + messages.length);

            // we need to parse message BEFORE flags are set!
            emails[i] = new ReceivedEmail(msg, envelope, session.attachmentStorage);

            if (!EmailUtil.isEmptyFlags(flagsToSet)) {
                emails[i].flags(flagsToSet);
                msg.setFlags(flagsToSet, true);
            }

            if (!EmailUtil.isEmptyFlags(flagsToUnset)) {
                emails[i].flags().remove(flagsToUnset);
                msg.setFlags(flagsToUnset, false);
            }

            if (EmailUtil.isEmptyFlags(flagsToSet) && !emails[i].isSeen()) {
                msg.setFlag(Flags.Flag.SEEN, false);
            }
        }

        if (processedMessageConsumer != null) {
            processedMessageConsumer.accept(messages);
        }

        // if messages were marked to be deleted, we need to expunge the folder
        if (!EmailUtil.isEmptyFlags(flagsToSet)) {
            if (flagsToSet.contains(Flags.Flag.DELETED)) {
                session.folder.expunge();
            }
        }

        return emails;
    }

    public static ReceivedEmails empty() {
        return new ReceivedEmails(null, new Message[0], null, null, false, null);
    }
}